/**
  ******************************************************************************
  * @file           : raster_if.c
  * @version        : v1.0
  * @brief          : usb device printer received taster handle.
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "raster_if.h"

/* USER CODE BEGIN INCLUDE */

/* USER CODE END INCLUDE */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
  * @brief Usb device library.
  * @{
  */

/** @addtogroup USBD_CDC_IF
  * @{
  */

/** @defgroup USBD_CDC_IF_Private_TypesDefinitions USBD_CDC_IF_Private_TypesDefinitions
  * @brief Private types.
  * @{
  */

/* USER CODE BEGIN PRIVATE_TYPES */

/* USER CODE END PRIVATE_TYPES */

/**
  * @}
  */

/** @defgroup USBD_CDC_IF_Private_Defines USBD_CDC_IF_Private_Defines
  * @brief Private defines.
  * @{
  */

/* USER CODE BEGIN PRIVATE_DEFINES */
/* USER CODE END PRIVATE_DEFINES */

/**
  * @}
  */

/** @defgroup USBD_CDC_IF_Private_Macros USBD_CDC_IF_Private_Macros
  * @brief Private macros.
  * @{
  */

/* USER CODE BEGIN PRIVATE_MACRO */

/* USER CODE END PRIVATE_MACRO */

/**
  * @}
  */

/** @defgroup USBD_CDC_IF_Private_Variables USBD_CDC_IF_Private_Variables
  * @brief Private variables.
  * @{
  */
uint8_t cmdKeyLst[] = 
{
  0x18,// CAN
  0x1b, // ESC 
  0x1c, // FS 
  0x1d, // GS 
  0x1f // US 
};

POS_CMD_DEF cmdDefLst[] =
{
  {0x09, 0},// HT
  {0x0A, 0},// LF
  {0x0C, 0},// FF
  {0x0D, 0},// CR
  {0x10, 2, 0x04},// DLE EOT n Real-time status transmission, NEED printer resp
  {0x10, 2, 0x05},// DLE ENQ n Real-time request to printer
  {0x10, 4, 0x14},// DLE DC4 n m t Real-time output of specified pulse
  {0x18, 0},// CAN  
  {0x1b, 1, 0x0c}, // ESC FF  Print data in page mode
  {0x1b, 2, 0x20}, // ESC SP n Set character right space amount
  {0x1b, 2, 0x21}, // ESC ! n Batch specify print mode
  {0x1b, 3, 0x24}, // ESC $ nL nH Specify absolute position
  {0x1b, 2, 0x25}, // ESC % n Specify/cancel download character set
  {0x1b, 0xff, 0x26, GetDlCharSet}, // ESC & y c1 c2 [x1 d1 ... d(y × x1)] ... [xk d1 ... d(y × xk)] Define download characters
  {0x1b, 0xff, 0x2a, GetBitImageMode}, // ESC * m nL nH d1...dk Specify bit image mode
  {0x1b, 1, 0x32}, // ESC 2 Set default line spacing
  {0x1b, 2, 0x33}, // ESC 3 n Set line feed amount
  {0x1b, 2, 0x3d}, // ESC = n Select peripheral device
  {0x1b, 2, 0x3f}, // ESC ? n Delete download characters
  {0x1b, 1, 0x40}, // ESC @ Initialize printer
  {0x1b, 0xff, 0x44, SetHorTab}, // ESC D n1...nk NUL Set horizontal tab position
  {0x1b, 2, 0x45}, // ESC E n Specify/cancel emphasized characters
  {0x1b, 2, 0x47}, // ESC G n Specify/cancel double printing
  
  {0x1b, 2, 0x4a}, // ESC J n  Print and Paper Feed
  {0x1b, 1, 0x4c}, // ESC L  Select page mode
  {0x1b, 2, 0x4d}, // ESC M n  Select character font

  // too many, skip them
  
  {0x1b, 4, 0x70}, // ESC p m t1 t2  Select character code table
  {0x1c, 2, 0x21}, // FS ! n Batch specify Chinese character print mode
  {0x1d, 0xff, 0x76, GetRaster}, // GS v 
  
  {0x1f, 1, 0x1c}, // US FS  unknown
  {0x1f, 5, 0x56}, // US V  unknown
};

PIC_FILE_t pic_file = {0, 0, 0};
uint16_t NetBytes2Word(net_w n)
{
    uint16_t w;
    w = ((uint16_t)n.byteH << 8) + (uint16_t)n.byteL;
    return w;
}

net_w Word2NetBytes(uint16_t w)
{
    net_w n;
    n.byteH = w >> 8;
    n.byteL = w & 0xff;
    return n;
}

bool isCmd(uint8_t chr)
{
  bool ret = false;

  for(int i = 0; i < sizeof(cmdKeyLst); i++)
  {
    if (chr == cmdKeyLst[i])
    {
      ret = true;
      return ret;
    }
  }

  return ret;
}

bool getCmdDef(const uint8_t* pbuf, POS_CMD_DEF *pCmdDef)
{
  bool ret = false;

  for(int i = 0; i < sizeof(cmdDefLst)/sizeof(POS_CMD_DEF); i++)
  {
    if (*pbuf != cmdDefLst[i].cmd)
    {
      continue;
    }

    if ((0 != cmdDefLst[i].code) && (*(pbuf + 1) != cmdDefLst[i].code))
    {
      continue;
    }

    *pCmdDef = cmdDefLst[i];
    ret = true;
    return ret;
    
  }

  return ret;
}

uint16_t GetDlCharSet(const uint8_t *pbuf, uint16_t len)
{
  // ESC & y c1 c2 [x1 d1 ... d(y × x1)] ... [xk d1 ... d(y × xk)] Define download characters
  ESC_DL_CHAR_SET_HEADER *pEscDlCharSet = (ESC_DL_CHAR_SET_HEADER *)pbuf;
  uint8_t *pBody = pEscDlCharSet->data;
  uint16_t cmdLen = sizeof(ESC_DL_CHAR_SET_HEADER), charSetLen = 0;
  uint8_t xn = 0;

  for(int i = 0; i < pEscDlCharSet->c2 - pEscDlCharSet->c1 + 1; i++)
  {
    // every char len = y * xn
    xn = *pBody;
    charSetLen = (uint16_t)pEscDlCharSet->y * xn;
    cmdLen += charSetLen;
    pBody += charSetLen;
  }

  return cmdLen;
}

uint16_t GetBitImageMode(const uint8_t *pbuf, uint16_t len)
{
  // ESC * m nL nH d1...dk Specify bit image mode
  ESC_BIT_IMG_MODE_HEADER *pEscBitImgMode = (ESC_BIT_IMG_MODE_HEADER *)pbuf;
  uint8_t yDot = 1; //Number of Hor. Dir. Dots

  switch(pEscBitImgMode->m) {
    case 0:
    case 1:
      yDot = 1;
      break;
      
    case 32:
    case 33:
      yDot = 3;
      break;

    default:
      break;
  }

  return sizeof(ESC_BIT_IMG_MODE_HEADER) + (pEscBitImgMode->nL + (uint16_t)pEscBitImgMode->nH * 256) * yDot;

}

uint16_t SetHorTab(const uint8_t *pbuf, uint16_t len)
{
  // ESC D n1...nk NUL Set horizontal tab position
  uint8_t *pBody = pbuf;

  while(NULL != *pBody++);

  return (pBody - pbuf + 1);
}


int8_t Raster_Cmd(const uint8_t* pbuf, uint16_t len)
{
  // check if cmd
  const uint8_t* sec = pbuf;
  POS_CMD_DEF cmdItem;
  

  while(getCmdDef(sec, &cmdItem))
  {
    if(cmdItem.cmdUsrHandle != NULL)
    {
      sec += cmdItem.cmdUsrHandle(sec, len);
    }
    else {
      sec += cmdItem.len + sizeof(cmdItem.cmd) + sizeof(cmdItem.code);
    }

    if (sec - pbuf >= len) break;
  }

  return 0;
}

uint8_t FmtRowColId(uint8_t *pBody, uint16_t inputId)
{
  uint8_t byteNum;
  
  if (inputId < 0x80)
  {
    *pBody = inputId;
    byteNum = 1;
  }
  else
  {
    *pBody = 0x80 + inputId / 0x80;
    *(pBody + 1) = inputId % 0x80;
    byteNum = 2;
  }

  return byteNum;
}

uint16_t GetRaster(const uint8_t *pbuf, uint16_t len)
{
  // GS  v  0  m xL xH yL yH d1...dk
  // 1d 76 30 00 30 00 18
  GS_V_PRINT_IMG_HEADER *pGSv = (GS_V_PRINT_IMG_HEADER *)pbuf;
  uint16_t xWidth, yHeight, iRow, iCol;
  const uint8_t *pByte = pbuf + sizeof(GS_V_PRINT_IMG_HEADER);
  uint8_t *pPicBody = NULL, *pPicRow = NULL;
  uint16_t colCnt = 0;
  uint8_t byteNum = 0;

  xWidth = ( pGSv->xH * 256 + pGSv->xL ) * 8;
  yHeight = pGSv->yH * 256 + pGSv->yL;

  if (NetBytes2Word(pic_file.x) < xWidth )
  {
    pic_file.x = Word2NetBytes(xWidth);       // get the picture's max width
  }
  pic_file.y = Word2NetBytes(NetBytes2Word(pic_file.y) + yHeight);      // accumulate the picture's height
 
  pPicBody = pic_file.data + NetBytes2Word(pic_file.bdlen);
  
  // the picture body
  for ( iRow = 0; iRow < yHeight; iRow++ )
  {
    pPicRow = NULL;
    colCnt = 0;
    
    for ( iCol = 0; iCol < xWidth / 8; iCol++ )
    {
      // len
      // iRow
      // ColArray
      if (*pByte != 0x00)
      {
        if(NULL == pPicRow)
        {
          pPicRow = pPicBody;
          
          //temporary consider rowLen is less than 0x80
          pPicBody++;  // len

          // row
          byteNum = FmtRowColId(pPicBody, iRow);
          pPicBody += byteNum;
        }

        for (uint8_t iBit = 0; iBit < 8; iBit++)
        {
            if (GetBit(*pByte, 7 - iBit))
            {
                byteNum = FmtRowColId(pPicBody, iCol * 8 + iBit);
                pPicBody += byteNum;
                colCnt++;
            }
        }
      }

      pByte++;
    }

    // update the row's length
    if(colCnt >= 0x80)
    {
      // increase len from 1 byte to 2 bytes, so move behind sector
      //      new row pos  old row pos, 
      memmove(pPicRow + 2, pPicRow + 1, pPicBody - pPicRow - 1);
      pPicBody++;
      
      FmtRowColId(pPicRow, colCnt);
    }
    else if (colCnt > 0)
    {
      *pPicRow = colCnt;
    }
  }

  pic_file.bdlen = Word2NetBytes(pPicBody - pic_file.data);

  return (pByte - pbuf);    // parse buf length
}

int8_t End_Raster()
{
    return 0;
}

int8_t Raster_Parse(uint8_t* pbuf, uint16_t len)
{
	return 0;
}

int8_t outputPic()
{
    // Open a file in binary write mode
    FILE* file = fopen("person_data.bin", "wb");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // Write the struct data to the file
    size_t num_written
        = fwrite(&pic_file, NetBytes2Word(pic_file.bdlen) + 6, 1, file);
    if (num_written != 1) {
        perror("Error writing to file");
        fclose(file);
        return 1;
    }

    // Close the file
    fclose(file);

    printf("Struct data written to Binary file "
        "successfully.\n");

    return 0;
}
